package com.origin.oss.operation.upload.product;

import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.fastjson2.JSON;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import com.origin.oss.exception.operation.UploadException;
import com.origin.oss.manager.AliOssUploadManager;
import com.origin.oss.operation.upload.domain.FileSliceUpload;
import com.origin.oss.specification.domain.multipart.SliceUploadInfo;
import com.origin.oss.specification.domain.multipart.response.CompleteSliceUploadResp;
import com.origin.oss.operation.upload.request.QiwenMultipartFile;
import com.origin.oss.util.AliyunUtils;
import com.origin.oss.config.AliyunConfig;
import com.origin.oss.enums.StorageType;
import com.origin.oss.operation.upload.FileUploader;
import com.origin.oss.util.RedisUtil;
import com.origin.oss.util.FileStorageUtils;
import com.origin.oss.util.concurrent.locks.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author origin
 */
@Component
@Slf4j
public class AliyunOSSUploader extends FileUploader {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedisLock redisLock;

    @Resource
    private AliOssUploadManager aliOssUploadManager;

    private final AliyunConfig aliyunConfig;

    public AliyunOSSUploader(AliyunConfig aliyunConfig) {
        this.aliyunConfig = aliyunConfig;
    }

    @Override
    protected StorageType getStorageType() {
        return StorageType.ALIYUN;
    }

    @Override
    protected boolean doSliceUpload(QiwenMultipartFile qiwenFile, FileSliceUpload fileSliceUpload) {
        String identifier = fileSliceUpload.getIdentifier();
        OSS ossClient = AliyunUtils.getOSSClient(aliyunConfig);

        SliceUploadInfo uploadFileInfo = aliOssUploadManager.getSliceUploadInfo(identifier);
        if (uploadFileInfo == null) {
            String fileUrl = qiwenFile.getFileUrl();
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliyunConfig.getOss().getBucketName(), fileUrl);
            InitiateMultipartUploadResult upResult = ossClient.initiateMultipartUpload(request);
            String uploadId = upResult.getUploadId();

            uploadFileInfo = new SliceUploadInfo();
            uploadFileInfo.setKey(fileUrl);
            uploadFileInfo.setUploadId(uploadId);
            uploadFileInfo.setBucketName(aliyunConfig.getOss().getBucketName());
            uploadFileInfo.setImage(qiwenFile.isImage());
            aliOssUploadManager.storeUploadPartRequest(uploadFileInfo, identifier);
        }

        try (InputStream is = qiwenFile.getUploadInputStream()) {
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(uploadFileInfo.getBucketName());
            uploadPartRequest.setKey(uploadFileInfo.getKey());
            uploadPartRequest.setUploadId(uploadFileInfo.getUploadId());
            uploadPartRequest.setInputStream(is);
            uploadPartRequest.setPartSize(qiwenFile.getSize());
            uploadPartRequest.setPartNumber(fileSliceUpload.getChunkNumber());
            log.debug(JSON.toJSONString(uploadPartRequest));

            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            log.debug("上传结果：{}", JSON.toJSONString(uploadPartResult));

            aliOssUploadManager.storePartETag(uploadPartResult.getPartETag(), identifier);
        } catch (IOException e) {
            log.error("分片上传失败 - identifier: {}, chunkNumber: {}", identifier, fileSliceUpload.getChunkNumber(), e);
            return false;
        } finally {
            ossClient.shutdown();
        }
        return true;
    }

    /**
     * 取消上传
     */
    @Override
    public void cancelUpload(FileSliceUpload uploadFile) {

        SliceUploadInfo uploadFileInfo = JSON.parseObject(redisUtil.getObject("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":uploadPartRequest"), SliceUploadInfo.class);

        OSS ossClient = AliyunUtils.getOSSClient(aliyunConfig);
        AbortMultipartUploadRequest abortMultipartUploadRequest =
                new AbortMultipartUploadRequest(aliyunConfig.getOss().getBucketName(),
                        uploadFileInfo.getKey(),
                        uploadFileInfo.getUploadId());
        ossClient.abortMultipartUpload(abortMultipartUploadRequest);
        ossClient.shutdown();
    }

    @Override
    public CompleteSliceUploadResp doFinishSliceUpload(String identifier) {
        SliceUploadInfo uploadFileInfo = aliOssUploadManager.getSliceUploadInfo(identifier);
        completeMultipartUpload(identifier, uploadFileInfo);
        aliOssUploadManager.clearCache(identifier);

        CompleteSliceUploadResp fileUploadResult = new CompleteSliceUploadResp();
        if (uploadFileInfo.isImage()) {
            OSS ossClient = AliyunUtils.getOSSClient(aliyunConfig);
            OSSObject ossObject = ossClient.getObject(aliyunConfig.getOss().getBucketName(),
                    FileStorageUtils.getAliyunObjectNameByFileUrl(uploadFileInfo.getKey()));
            InputStream is = ossObject.getObjectContent();
            BufferedImage srbufferedImage;
            try {
                srbufferedImage = ImageIO.read(is);
                fileUploadResult.setBufferedImage(srbufferedImage);
            } catch (IOException e) {

            } finally {
                IOUtils.closeQuietly(is);
            }
        }
        return fileUploadResult;
    }

    @Override
    protected boolean checkFinish(FileSliceUpload fileSliceUpload) {
        return aliOssUploadManager.countPartETag(fileSliceUpload.getIdentifier()) == fileSliceUpload.getTotalChunks();
    }

    /**
     * 将文件分块进行升序排序并执行文件上传。
     */
    private void completeMultipartUpload(String identifier, SliceUploadInfo uploadFileInfo) {
        List<PartETag> partETags = aliOssUploadManager.listPartETag(identifier);
        partETags.sort(Comparator.comparingInt(PartETag::getPartNumber));

        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(aliyunConfig.getOss().getBucketName(),
                        uploadFileInfo.getKey(),
                        uploadFileInfo.getUploadId(),
                        partETags);
        OSS ossClient = AliyunUtils.getOSSClient(aliyunConfig);
        // 完成上传
        ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        ossClient.shutdown();
    }

    //    @Override
//    protected FileUploadResult doUpload(QiwenMultipartFile qiwenMultipartFile, FileSliceUpload uploadFile) {
//        try {
//            rectifier(qiwenMultipartFile, uploadFile);
//            return organizationalResults(qiwenMultipartFile, uploadFile);
//        } catch (Exception e) {
//            throw new UploadException(e);
//        }
//    }

    private void rectifier(QiwenMultipartFile qiwenMultipartFile, FileSliceUpload uploadFile) {
        String key = "QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":lock";
        String currentUploadChunkNumberKey = "QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":current_upload_chunk_number";

        // todo 判断当前的 chunkNumber 是否已经存在; 如果存在, 表示当前分片已经传过了, 直接跳过; 否则, 将当前的 chunkNumber 存入 redis
        redisLock.lock(key);
        try {
            int currentUploadChunkNumber = 1;
            String curChunkNum = redisUtil.getObject(currentUploadChunkNumberKey);
            if (CharSequenceUtil.isBlank(curChunkNum)) {
                redisUtil.set(currentUploadChunkNumberKey, "1", 1000 * 60 * 60);
            } else {
                currentUploadChunkNumber = Integer.parseInt(curChunkNum);
            }

            if (uploadFile.getChunkNumber() != currentUploadChunkNumber) {
                redisLock.unlock(key);
                Thread.sleep(100);
                while (redisLock.tryLock(key, 300, TimeUnit.SECONDS)) {
                    currentUploadChunkNumber = Integer.parseInt(redisUtil.getObject(currentUploadChunkNumberKey));
                    if (uploadFile.getChunkNumber() <= currentUploadChunkNumber) {
                        break;
                    } else {
                        if (Math.abs(currentUploadChunkNumber - uploadFile.getChunkNumber()) > 2) {
                            log.error("传入的切片数据异常，当前应上传切片为第 {} 块, 传入的为第 {} 块。", currentUploadChunkNumber, uploadFile.getChunkNumber());
                            throw new UploadException("传入的切片数据异常");
                        }
                        redisLock.unlock(key);
                    }
                }
            }

            log.info("文件名 {}, 正在上传第 {} 块, 共 {} 块>>>>>>>>>>", qiwenMultipartFile.getMultipartFile().getOriginalFilename(), uploadFile.getChunkNumber(), uploadFile.getTotalChunks());
            if (uploadFile.getChunkNumber() == currentUploadChunkNumber) {
                doSliceUpload(qiwenMultipartFile, uploadFile);
                log.info("文件名 {}, 第 {} 块上传成功", qiwenMultipartFile.getMultipartFile().getOriginalFilename(), uploadFile.getChunkNumber());
                this.redisUtil.getIncr("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":current_upload_chunk_number");
            }
        } catch (Exception e) {
            log.error("第 {} 块上传失败, 自动重试", uploadFile.getChunkNumber());
            redisUtil.set("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":current_upload_chunk_number", String.valueOf(uploadFile.getChunkNumber()), 1000 * 60 * 60);
            throw new UploadException("更新远程文件出错", e);
        } finally {
            redisLock.unlock(key);
        }
    }

    @Override
    protected CompleteSliceUploadResp organizationalResults(QiwenMultipartFile qiwenMultipartFile, FileSliceUpload uploadFile) {
        CompleteSliceUploadResp uploadFileResult = new CompleteSliceUploadResp();
//        UploadFileInfo uploadFileInfo = JSON.parseObject(redisUtil.getObject("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":uploadPartRequest"), UploadFileInfo.class);
//
//        uploadFileResult.setFileUrl(uploadFileInfo.getKey());
//        uploadFileResult.setFileName(qiwenMultipartFile.getFileName());
//        uploadFileResult.setExtendName(qiwenMultipartFile.getExtendName());
//        uploadFileResult.setFileSize(uploadFile.getTotalSize());
//        if (uploadFile.getTotalChunks() == 1) {
//            uploadFileResult.setFileSize(qiwenMultipartFile.getSize());
//        }
//        uploadFileResult.setStorageType(StorageTypeEnum.ALIYUN_OSS);
//        uploadFileResult.setIdentifier(uploadFile.getIdentifier());
//        if (uploadFile.getChunkNumber() == uploadFile.getTotalChunks()) {
//            log.info("分片上传完成");
//            completeMultipartUpload(uploadFile.getIdentifier(), uploadFileInfo);
//            redisUtil.deleteKey("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":current_upload_chunk_number");
//            redisUtil.deleteKey("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":partETags");
//            redisUtil.deleteKey("QiwenUploader:Identifier:" + uploadFile.getIdentifier() + ":uploadPartRequest");
//            if (UFOPUtils.isImageFile(uploadFileResult.getExtendName())) {
//                OSS ossClient = AliyunUtils.getOSSClient(aliyunConfig);
//                OSSObject ossObject = ossClient.getObject(aliyunConfig.getOss().getBucketName(),
//                        UFOPUtils.getAliyunObjectNameByFileUrl(uploadFileResult.getFileUrl()));
//                InputStream is = ossObject.getObjectContent();
//                BufferedImage src;
//                try {
//                    src = ImageIO.read(is);
//                    uploadFileResult.setBufferedImage(src);
//                } catch (IOException e) {
//
//                } finally {
//                    IOUtils.closeQuietly(is);
//                }
//
//            }
//            uploadFileResult.setStatus(FileUploadStateEnum.SUCCESS);
//        } else {
//            uploadFileResult.setStatus(FileUploadStateEnum.UNCOMPLETE);
//
//        }
        return uploadFileResult;
    }


}
